package token

import (
	"context"
	"fmt"
	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/golang-jwt/jwt/v5"
	"time"
	"zuhao-seucre/internal/boot/cache"
	"zuhao-seucre/internal/common/model"
	"zuhao-seucre/internal/consts"
	"zuhao-seucre/internal/service"
	"zuhao-seucre/utility/simple"
)

type sToken struct {
}

func New() *sToken {
	return &sToken{}
}

func init() {
	service.RegisterToken(New())
}

func (s *sToken) CreateToken(ctx context.Context, userModel *model.UserModel) (accessToken string, refreshToken string, err error) {
	tokenConfig := service.Config().MustGetTokenConf()
	now := gtime.Now()
	// 有效时长
	expires := time.Minute * gconv.Duration(tokenConfig.Expires)
	// 刷新时长
	refreshTokenExpireTime := time.Minute * gconv.Duration(tokenConfig.RefreshTokenExpireTime)

	// 带权限创建令牌
	// 设置有效期，过期需要重新登录获取token
	accessJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       userModel.UserId,
		"username": userModel.Username,
		"exp":      now.Add(time.Minute * gconv.Duration(tokenConfig.Expires)).Unix(),
	})
	// refresh token
	refreshJwt := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       userModel.UserId,
		"username": userModel.Username,
		"exp":      now.Add(time.Minute * time.Duration(tokenConfig.RefreshTokenExpireTime)).Unix(),
	})
	// 使用自定义字符串加密 and get the complete encoded token as a string
	accessToken, err = accessJwt.SignedString([]byte(tokenConfig.SecretKey))
	if err != nil {
		return
	}
	refreshToken, err = refreshJwt.SignedString([]byte(tokenConfig.SecretKey))
	var (
		// 认证key
		authKey = GetAuthKey(accessToken)
		// 登录token
		tokenKey = GetTokenKey(authKey)
		// 刷新token
		refreshAuthKey = GetAuthKey(refreshToken)
		// 登录token
		refreshTokenKey = GetRefreshToken(refreshAuthKey)
		// 身份绑定
		bindKey = GetBindKey(userModel.UserId)
	)
	// 把这个缓存起来是将来做刷新token用的
	token := &model.TokenValue{
		ExpireAt:     now.Add(time.Minute * gconv.Duration(tokenConfig.Expires)).Unix(),
		RefreshAt:    now.Unix(),
		RefreshCount: 0,
	}

	if err = cache.Instance().Set(ctx, tokenKey, token, expires); err != nil {
		return "", "", err
	}

	if err = cache.Instance().Set(ctx, refreshTokenKey, userModel, refreshTokenExpireTime); err != nil {
		return "", "", err
	}

	if err = cache.Instance().Set(ctx, bindKey, tokenKey, expires); err != nil {
		return "", "", err
	}
	return accessToken, refreshToken, nil
}

var (
	errorLogin      = gerror.New("登录身份已失效，请重新登录！")
	errorMultiLogin = gerror.New("账号已在其他地方登录，如非本人操作请及时修改登录密码！")
)

func (s *sToken) ParseLoginUser(r *ghttp.Request) (user *model.UserModel, err error) {
	tokenConfig := service.Config().MustGetTokenConf()
	var (
		ctx    = r.Context()
		header = GetAuthorization(r)
	)
	if header == "" {
		err = errorLogin
		return
	}
	userId, username, err := s.ParseToken(ctx, header)
	if err != nil {
		g.Log().Debugf(ctx, "parseToken err:%+v", err)
		err = errorLogin
		return
	}
	var (
		// 认证key
		authKey = GetAuthKey(header)
		// 登录token
		tokenKey = GetTokenKey(authKey)
		// 身份绑定
		bindKey = GetBindKey(userId)
	)
	tk, err := cache.Instance().Get(ctx, tokenKey)

	if err != nil {
		g.Log().Debugf(ctx, "get tokenKey err:%+v", err)
		err = errorLogin
		return
	}

	if tk.IsEmpty() {
		g.Log().Debug(ctx, "token isEmpty")
		err = errorLogin
		return
	}

	var token *model.TokenValue
	if err = tk.Scan(&token); err != nil {
		g.Log().Debugf(ctx, "token scan err:%+v", err)
		err = errorLogin
		return
	}

	if token == nil {
		g.Log().Debugf(ctx, "token = nil")
		err = errorLogin
		return
	}

	now := gtime.Now()
	if token.ExpireAt < now.Unix() {
		g.Log().Debugf(ctx, "token expired.")
		err = errorLogin
		return
	}

	// 是否允许多端登录
	if !service.Config().MustGetTokenConf().MultiLogin {
		origin, err := cache.Instance().Get(ctx, bindKey)
		if err != nil {
			g.Log().Debugf(ctx, "bindKey get err:%+v", err)
			err = errorLogin
			return nil, err
		}

		if origin == nil || origin.IsEmpty() {
			g.Log().Debug(ctx, "bindKey isEmpty")
			err = errorLogin
			return nil, err
		}

		if tokenKey != origin.String() {
			g.Log().Debugf(ctx, "bindKey offsite login tokenKey:%v, origin:%v", tokenKey, origin.String())
			err = errorMultiLogin
			return nil, err
		}
	}

	// 自动刷新token有效期
	refreshToken := func() {
		// 未开启自动刷新
		if !tokenConfig.AutoRefresh {
			return
		}

		// 刷新次数已达上限
		if tokenConfig.MaxRefreshTimes != -1 && token.RefreshCount >= tokenConfig.MaxRefreshTimes {
			return
		}

		// 未达到刷新间隔
		if gtime.New(token.RefreshAt).Unix()+tokenConfig.RefreshInterval > now.Unix() {
			return
		}

		// 刷新有效期
		token.ExpireAt = now.Unix() + tokenConfig.Expires
		token.RefreshAt = now.Unix()
		token.RefreshCount += 1

		duration := time.Second * gconv.Duration(tokenConfig.Expires)

		if err = cache.Instance().Set(ctx, tokenKey, token, duration); err != nil {
			return
		}

		if err = cache.Instance().Set(ctx, bindKey, tokenKey, duration); err != nil {
			return
		}
	}

	simple.SafeGo(ctx, func(ctx context.Context) {
		refreshToken()
	})

	user = &model.UserModel{
		UserId:   userId,
		Username: username,
	}
	return
}

func (s *sToken) ParseToken(ctx context.Context, tokenStr string) (uint64, string, error) {
	tokenConfig := service.Config().MustGetTokenConf()
	if tokenStr == "" {
		return 0, "", gerror.New("token error")
	}
	// Parse token
	token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (any, error) {
		return []byte(tokenConfig.SecretKey), nil
	})
	if err != nil || token == nil {
		return 0, "", err
	}
	if !token.Valid {
		return 0, "", gerror.New("token invalid")
	}
	i := token.Claims.(jwt.MapClaims)
	return uint64(i["id"].(float64)), i["username"].(string), nil
}

// GetAuthorization 获取authorization
func GetAuthorization(r *ghttp.Request) string {
	// 默认从请求头获取
	var authorization = r.Header.Get("Authorization")

	// 如果请求头不存在则从get参数获取
	if authorization == "" {
		return r.Get("authorization").String()
	}
	return gstr.Replace(authorization, "Bearer ", "")
}

// GetAuthKey 认证key
func GetAuthKey(token string) string {
	return gmd5.MustEncryptString("zuHao" + token)
}

// GetTokenKey 令牌缓存key
func GetTokenKey(authKey string) string {
	return fmt.Sprintf("%v:%v", consts.CacheToken, authKey)
}

func GetRefreshToken(authKey string) string {
	return fmt.Sprintf("%v:%v", consts.GetRefreshToken, authKey)
}

// GetBindKey 令牌身份绑定key
func GetBindKey(userId uint64) string {
	return fmt.Sprintf("%v:%v", consts.CacheTokenBind, userId)
}
